iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
自我挑戰組

自己的 Leak, 自己抓(swift)系列 第 10

第三層 Visitor (Closure Visitor)

  • 分享至 

  • xImage
  •  

昨天我們從零散 ID 升級到各個宣告(global/class/...)的 零散 ID

https://ithelp.ithome.com.tw/upload/images/20230924/20158030HnaVaTfdbW.png

於是我們可在仿照 昨天,一層 visitor 解決不了。

繼續在中間加一層 Visitor,近一步搜集 closure。


我們希望他能夠依造他所在的階層,蒐集只有出現該階層的 ID

fileprivate final class IdentifierVisitor: SyntaxVisitor {
  lazy var ids: [IdentifierExprSyntax] = []
  
  override final func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
    return .skipChildren
  }
  override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
    return .skipChildren
  }
  
  override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
    return .skipChildren
  }
  override final func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind {
    ids.append(node)
    return .skipChildren
  }
}

fileprivate final class ClosureVisitor: SyntaxVisitor {
  lazy var idVisitor: IdentifierVisitor = IdentifierVisitor(viewMode: .sourceAccurate)
  private lazy var subVisitors: [ClosureVisitor] = []
  
  override final func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
    return .skipChildren
  }
  override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
    append(node)
    return .skipChildren
  }
  
  override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
    append(node)
    return .skipChildren
  }
  
  public final func append(_ node: ClosureExprSyntax) {
      let visitor = ClosureVisitor(viewMode: .sourceAccurate)
      self.subVisitors.append(visitor)
    if let sig = node.signature {
      super.walk(sig)
      self.idVisitor.walk(sig)
    }
    
    visitor.walk(node.statements)
    self.idVisitor.walk(node.statements)
  }
  

  private final func append(_ node: FunctionDeclSyntax) {
      let visitor = ClosureVisitor(viewMode: .sourceAccurate)
      self.subVisitors.append(visitor)
    if let body = node.body {
      visitor.walk(body)
      idVisitor.walk(body)
    }
  }
  
  var all: [ClosureVisitor] {
    return [self] + subVisitors.flatMap(\.all)
  }
}

fileprivate final class DeclVisitor: SyntaxVisitor {
  lazy var closureVisitor = ClosureVisitor(viewMode: .sourceAccurate)
  private lazy var subVisitors: [DeclVisitor] = []
  
  override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
    append(node.members)
    return .skipChildren
  }
  
  public final func customWalk<SyntaxType>(_ node: SyntaxType) where SyntaxType: SyntaxProtocol {
      super.walk(node)
      self.closureVisitor.walk(node)
      self.closureVisitor.idVisitor.walk(node)
  }

  private final func append<Syntax: SyntaxProtocol>(_ syntax: Syntax) {
      let visitor = DeclVisitor(viewMode: .sourceAccurate)
      self.subVisitors.append(visitor)
      visitor.customWalk(syntax)
  }
  
  var all: [DeclVisitor] {
    return [self] + subVisitors.flatMap(\.all)
  }
  
  var ids: [[String]] {
    let result = self.closureVisitor.all.map(\.idVisitor.ids)
    return result.map {
      $0.map {
        $0.withoutTrivia().description
      }
    }
  }
}

private func parse(_ code: String) -> [DeclVisitor] {
  let ast = Parser.parse(source: code)
  let visitor = DeclVisitor(viewMode: .sourceAccurate)
  visitor.customWalk(ast)
  return visitor.all
}

Test

import Foundation
import SwiftSyntax
import SwiftParser
import XCTest

final class ClosureTests: XCTestCase {
  func testCode() {
    let code = """
    import Foundation
    
    let global = Out()

    class Out {
      func test() {
        print(global)
      }
    }

    func test() {
      class In {
        let a = 1
        func test(a: String) {
          let inner = In()
          print(self.a, a, 123)
          func test2() {
            escape {
              nonescape {
                self.test(a: "leak")
                print(inner)
              }
            }
          }
        }
      }
    }

    func escape(block: @escaping () -> Void) {}
    func nonescape(block: () -> Void) {}
    """
    
    let expect = [
      /// global
      [
        // let global = Out()
        ["Out"],
        /// func test() {
        [],
        /// func escape(block: @escaping () -> Void) {}
        [],
        /// func nonescape(block: () -> Void) {}
        [],
      ],
      /// Out
      [
        /// print(global)
        ["print", "global",],
        [],
      ],
      /// In
      [
        /// func test(a: String) {
        [
          /// let inner = In()
          "In",
          /// print(self.a, a, 123)
          "print", "self", "a",
        ],
        /// func test2() {
        ["escape"],
        /// escape {
        ["nonescape"],
        /// nonescape {
        [
          ///  self.test(a: "leak")
          "self",
          ///  print(inner)
          "print", "inner",
        ],
        [],
      ]
    ]
    let result = parse(code).map(\.ids)
    
    
    XCTAssertEqual(result, expect)
  }
}

結果

因為昨天實作的 DeclVisitor 的緣故。

我們可以集中聚焦於 Decl(global/class/...) 的內容

再透過今天的 ClosureVisitor

我們可以關注到 closure 的階層關係。

https://ithelp.ithome.com.tw/upload/images/20230924/20158030Dzrh7M9dFA.png

https://ithelp.ithome.com.tw/upload/images/20230924/20158030IpJ6mjORvr.png


上一篇
一個 Visitor 解決不了,那就再加一個(Decl Visitor)
下一篇
掃瞄完 ID 之後~
系列文
自己的 Leak, 自己抓(swift)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言